Skip to content

fix: three live rig-package defects (#701, #702, #703) - #706

Merged
chubes4 merged 3 commits into
mainfrom
fix/701-702-703-live-rig-defects
Aug 1, 2026
Merged

chubes4 merged 3 commits into
mainfrom
fix/701-702-703-live-rig-defects

Conversation

@chubes4

@chubes4 chubes4 commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Fixes three reported rig-package defects. One of the three (#702) is refuted — the reported files are not broken, but the sweep it prompted found a different, real defect. Details below.

Found during the 2026-08-01 consolidation investigation (Extra-Chill/homeboy#11151).


#701 — stale rig-id env literal (CONFIRMED)

Automattic/studio/rigs/studio-agent-claude-trunk/rig.json declared:

"path": "${env.HOMEBOY_RIG_COMPONENT_PATH__STUDIO_AGENT_CLAUDE_EVALTIMING__STUDIO}"

while the rig id is studio-agent-claude-trunk. Homeboy derives the canonical override name from (rig_id, component_id) in rig_component_path_override_env_name (crates/homeboy-rig/src/expand.rs:241-247), so the variable that exists at runtime is ..._STUDIO_AGENT_CLAUDE_TRUNK__STUDIO. The rig was renamed from studio-agent-claude-evaltiming and the hand-written literal was never updated.

The literal was dead in both directions:

  • when the canonical override is set, resolve_component_path returns it first and unconditionally (component_resolution.rs:34) — before path is consulted at all;
  • when it is not set, ${env.…_EVALTIMING__STUDIO} expands to the empty string and resolution falls through to registry lookup.

Fix: deleted the literal rather than correcting it. path is optional (#[serde(default, skip_serializing_if)] on ComponentSpec) and the env override already applies unconditionally, so it was pure redundancy — and that redundancy is precisely what let the name drift undetected. No path_setting was present on this component. Behavior is unchanged in both cases.

#702 — Gutenberg imports (REFUTED — but the sweep found a real one)

The four reported Gutenberg workloads are not broken. WordPress/gutenberg/shared/wp-codebox is a symlink to ../../../shared/wp-codebox, tracked in git as mode 120000 (blob 13d7ff06). So ../shared/wp-codebox/recipe.mjs from WordPress/gutenberg/bench/ resolves through that symlink to the repo-root shared/wp-codebox/recipe.mjs, which exists.

Verified by actually importing all four under node: each runs its import graph to completion and fails only on its runtime env guard (HOMEBOY_COMPONENT_PATH is required), which proves the imports resolved. No change made to those four files.

Also worth noting the premise that "correct depth is three levels" is not universal — Automattic/studio/bench/lib/visual-fidelity.mjs correctly uses four (../../../../shared/...), because depth depends on the file's own location. Any future lint rule must resolve, not pattern-match.

The sweep did find one genuinely unresolvable import, unrelated to Gutenberg:

WordPress/wordpress-develop/tools/fuzz-workload-validator.mjs:1
  export { … } from './core-fuzz-contract-validator.mjs';   // target does not exist

That target was deleted by 8ce2060 ("Consolidate WordPress rigs on Codebox primitives", #684), which removed the implementation but left this one-line re-export shim behind. Nothing in the repo imports the shim — it is a dangling orphan whose entire content is an unresolvable specifier. Deleted, along with the now-empty tools/ directory.

#703 — duplicate lifecycle key (CONFIRMED)

woocommerce/woocommerce/rigs/woocommerce-performance/woocommerce-wp-codebox-target.base.json declared "lifecycle" twice (:2-7 and :65-69). serde_json is last-wins, so the second block silently overwrote the first and "intent": "external" was dropped from the materialized spec.

Merged into a single block at the original (first) position:

  • intent: "external" restored. It is declared exactly once across both blocks, so there is no conflict on this field.
  • reason taken from the second block. The two differ only in specificity ("component checkouts are user-owned" vs. "the WooCommerce component checkout is user-owned"). The second is the local refinement and is the string actually in effect on main today, so the human-readable text does not regress.

On the ambiguity

It is low, and here is the evidence rather than a guess: shared/wordpress-plugin/wp-codebox-external-cleanup.base.json contains the first block verbatim, so that block was copy-pasted from the shared external-cleanup base — and this file is itself named …-wp-codebox-target.base.json. external is the house pattern for exactly this kind of target. The only field that had to be adjudicated was reason, and there I kept the currently-effective string.

Runtime impact is nil, which is worth stating plainly so this is not mistaken for a behavior change in cleanup: RigCleanupSpec::resource_cleanup_intent maps both External and an absent intent to ResourceCleanupIntent::DryRun (crates/homeboy-rig/src/spec.rs), and RigCleanupIntent is not consumed anywhere else in homeboy. The dropped field was declarative/documentary. Restoring it makes declared cleanup ownership survive into the materialized spec without changing what cleanup does.


Why CI did not catch these

homeboy rig lint follows filenames rather than the materialized spec, and has no import resolution — tracked upstream as Extra-Chill/homeboy#11148. Demonstrated directly against origin/main with the defects still present:

  • homeboy rig lint woocommerce/woocommerce --all → 0 failures with the duplicate lifecycle key in place. The "rig package JSON specs parse" step passes because serde_json accepts duplicates by design (last-wins); nothing compares the parsed spec against the source text.
  • No lint step resolves .mjs import specifiers, so the orphaned shim was invisible.
  • No lint step cross-checks a HOMEBOY_RIG_COMPONENT_PATH__… literal against the rig's own id, so the EVALTIMING/TRUNK drift was invisible.

Repo-wide sweeps

All three defect classes were swept exhaustively, not just the reported instances.

Sweep Method Result
Stale rig-id env literals Parsed every rig.json, resolved extends chains to attribute base files to their consuming rig ids, recomputed sanitize_env_segment(rig_id)__sanitize_env_segment(component_id) and compared to every HOMEBOY_RIG_COMPONENT_PATH__ literal 1 found (studio-agent-claude-trunk) — fixed. All other literals self-consistent.
Unresolvable relative imports Walked all 106 .mjs/.js, extracted every relative import / export … from / dynamic import() specifier (138 total), resolved each against the filesystem following symlinks 1 found (fuzz-workload-validator.mjs) — fixed. Now 0 broken.
Duplicate JSON keys json.load with an object_pairs_hook flagging repeats at every nesting level, across all 160 JSON files 1 found (woocommerce-wp-codebox-target.base.json) — fixed. Now 0.

Follow-up worth considering (not changed here)

Every remaining "path": "${env.HOMEBOY_RIG_COMPONENT_PATH__<SELF>__<COMP>}" literal in the repo is self-consistent but equally redundant, for the same reason as #701: the override is applied unconditionally ahead of path, and absent the env var the literal expands to empty and falls through to registry lookup. They are correct today and are left alone, but they are the same drift hazard. Worth a follow-up to prune them, ideally alongside a lint rule from Extra-Chill/homeboy#11148.

Validation

  • All 160 JSON files parse (python3 -m json.tool); duplicate-key sweep clean.
  • All .mjs pass node --check; import sweep reports 0 broken.
  • homeboy rig lint --all on woocommerce/woocommerce, WordPress/wordpress-develop, WordPress/gutenberg: 0 failures.
  • Automattic/studio reports 1 failure (RigCleanupSpec untagged-enum schema skew) — pre-existing and identical on origin/main, verified by linting a clean origin/main extract with the same binary (v0.327.0). Not introduced here.
  • No cargo invoked.

chubes4 added 3 commits August 1, 2026 14:18
…runk

The `studio` component declared:

    "path": "${env.HOMEBOY_RIG_COMPONENT_PATH__STUDIO_AGENT_CLAUDE_EVALTIMING__STUDIO}"

but the rig `id` is `studio-agent-claude-trunk`. Homeboy derives the
canonical override variable from `(rig_id, component_id)` in
`rig_component_path_override_env_name`
(crates/homeboy-rig/src/expand.rs:241-247), so the variable that actually
exists at runtime is `..._STUDIO_AGENT_CLAUDE_TRUNK__STUDIO`. The rig was
renamed from `studio-agent-claude-evaltiming` and this hand-written literal
was never updated, so the declared path was dead in every case:

- when the canonical override IS set, `resolve_component_path` returns it
  first and unconditionally (component_resolution.rs:34), before `path` is
  ever consulted; and
- when it is NOT set, `${env.…_EVALTIMING__STUDIO}` expands to the empty
  string and resolution falls through to registry lookup.

Rather than correcting the literal, delete it. `path` is optional
(`#[serde(default, skip_serializing_if)]` on `ComponentSpec`) and the env
override already applies unconditionally, so the literal was pure
redundancy — and that redundancy is exactly what allowed the name to drift
undetected. Removing it makes the rename-safe path the only path.

No `path_setting` was present on this component, so nothing else to prune.
Behavior is unchanged in both the override-set and override-unset cases.

Swept every `HOMEBOY_RIG_COMPONENT_PATH__` literal in the repo against its
own rig id and component key; this was the only stale one. The remaining
literals are self-consistent (redundant, but correct).

Closes #701
…port shim

The four Gutenberg workloads reported in #702 are NOT broken. Refuted by
direct verification:

`WordPress/gutenberg/shared/wp-codebox` is a symlink to
`../../../shared/wp-codebox`, tracked in git as mode 120000
(blob 13d7ff0). So `../shared/wp-codebox/recipe.mjs` from
`WordPress/gutenberg/bench/` resolves through that symlink to the
repo-root `shared/wp-codebox/recipe.mjs`, which exists. Importing all four
modules under node succeeds — each one runs to completion of its import
graph and fails only on its runtime env guard
("HOMEBOY_COMPONENT_PATH is required"), which is proof the imports
resolved. No change was made to those four files.

The `../../../shared/...` form used by the woocommerce and studio
workloads is the same target reached without the symlink hop; both are
correct, and depth is not uniform (studio's `bench/lib/` correctly uses
four levels).

The repo-wide sweep that was supposed to confirm #702 did find one real
broken import, unrelated to Gutenberg:

    WordPress/wordpress-develop/tools/fuzz-workload-validator.mjs:1
      export { … } from './core-fuzz-contract-validator.mjs';

That target was deleted by 8ce2060 ("Consolidate WordPress rigs on Codebox
primitives", #684), which removed the implementation but left this
one-line re-export shim behind. Nothing in the repo imports the shim, so
it is a dangling orphan whose only content is an unresolvable specifier.
Deleted, along with the now-empty `tools/` directory.

Sweep method: walked all 106 `.mjs`/`.js` files, extracted every relative
`import`/`export … from`/dynamic `import()` specifier (138 total) and
resolved each against the filesystem (following symlinks). After this
change: 0 broken.

Closes #702
… base

`woocommerce-wp-codebox-target.base.json` declared `"lifecycle"` twice — at
:2-7 and again at :65-69. `serde_json` is last-wins, so the second block
silently overwrote the first and the `"intent": "external"` declared in the
first was dropped from the materialized spec.

Merged into a single block at the original (first) position:

- `intent: "external"` is restored. It is declared exactly once across both
  blocks, so there is no conflict on this field. It is also corroborated as
  the deliberate value: `shared/wordpress-plugin/wp-codebox-external-cleanup.base.json`
  contains the first block verbatim, so that block was copy-pasted from the
  shared external-cleanup base — and this file is itself named
  `…-wp-codebox-target.base.json`.
- The `reason` is taken from the SECOND block. The two differ only in
  specificity ("component checkouts are user-owned" vs. "the WooCommerce
  component checkout is user-owned"); the second is the local refinement and
  is the string that is actually in effect on main today, so keeping it means
  the human-readable text does not regress.

Runtime impact is nil, which is why this went unnoticed:
`RigCleanupSpec::resource_cleanup_intent` maps both `External` and an absent
`intent` to `ResourceCleanupIntent::DryRun` (crates/homeboy-rig/src/spec.rs),
and `RigCleanupIntent` is not consumed anywhere else in homeboy. So the
dropped field was declarative/documentary rather than behavioral. Restoring
it makes the declared cleanup ownership survive into the materialized spec
without changing what cleanup actually does.

Swept all 160 JSON files in the repo with `json.load` under an
`object_pairs_hook` that flags repeated keys at every nesting level. This was
the only file affected; after this change the sweep reports 0.

Closes #703
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant